import { HttpContext } from '@angular/common/http';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ALLOW_ANONYMOUS } from '@delon/auth';
import { UploadWidgetModule } from '@delon/form/widgets/upload';
import { _HttpClient, RAW_BODY } from '@delon/theme';
import { NzButtonModule } from 'ng-zorro-antd/button';
import { NzListModule } from 'ng-zorro-antd/list';
import { NzMessageService } from 'ng-zorro-antd/message';
import { NzModalRef } from 'ng-zorro-antd/modal';
import { NzProgressModule } from 'ng-zorro-antd/progress';
import { NzSpaceModule } from 'ng-zorro-antd/space';
import { NzSpinModule } from 'ng-zorro-antd/spin';
import { NzTagModule } from 'ng-zorro-antd/tag';
import { NzUploadFile, NzUploadModule } from 'ng-zorro-antd/upload';
import pLimit from 'p-limit';
import { firstValueFrom, lastValueFrom } from 'rxjs';
import { Api, ResponseBody } from 'src/app/shared/api';
import { ResponseCode } from 'src/app/shared/response.code';
import {
  ChunkFileUrlType,
  FileTableDataType,
  UploadFileInfo,
  UploadProgressCallback,
  convertFileSizeUnit,
  tagMap
} from 'src/app/shared/utils/file';
import { MerkleTree } from 'src/app/shared/utils/file/MerkleTree';
import cutFile from 'src/app/shared/utils/file/cutFile';

@Component({
  selector: 'app-dashboard-upload',
  templateUrl: './upload.component.html',
  changeDetection: ChangeDetectionStrategy.Default,
  standalone: true,
  imports: [NzSpinModule, NzProgressModule, NzButtonModule, NzUploadModule, UploadWidgetModule, NzListModule, NzTagModule, NzSpaceModule]
})
export class UploadComponent {
  record: any = {};
  data: any = {};
  parentId: string = '0';
  fileList: NzUploadFile[] = [];
  uploadFileList: FileTableDataType[] = [];
  limit = pLimit(3);

  uploading = false;

  constructor(
    private modal: NzModalRef,
    private msgSrv: NzMessageService,
    public http: _HttpClient
  ) {
    const config = this.modal.getConfig();
    if (config && config.nzData) {
      this.parentId = config.nzData?.parentId;
      if (!this.parentId) {
        this.parentId = '0';
      }
    }
  }

  close() {
    this.modal.close(true);
  }

  getParentId(): Object {
    debugger;
    return { parentId: this.parentId };
  }

  onBeforeUpload = (file: NzUploadFile): boolean => {
    debugger;
    this.fileList = this.fileList.concat(file);
    this.uploadFileList.push({
      uid: file.uid,
      name: file.name,
      size: file.size || 0,
      unitSize: convertFileSizeUnit(file.size),
      md5: '',
      md5Progress: 0,
      progress: 0,
      file: file as unknown as File,
      chunkCount: 0,
      chunkFileList: [],
      uploadedSize: 0,
      status: 'loaded',
      statusTag: tagMap.loaded
    });
    return false;
  };

  async onUpload() {
    try {
      this.uploading = true;
      for (let i = 0; i < this.uploadFileList.length; i++) {
        const uploadInfo = this.uploadFileList[i];
        await this.uploadFile(uploadInfo.file, (process, status, md5, md5Progress) => {
          uploadInfo.uploadedSize += process;
          uploadInfo.progress = Math.floor((uploadInfo.uploadedSize / uploadInfo.size) * 100);
          uploadInfo.statusTag = tagMap[status];
          if (md5) uploadInfo.md5 = md5;
          if (md5Progress) uploadInfo.md5Progress = md5Progress;
        });
      }
      this.uploading = false;
    } catch (error) {
      this.uploading = false;
      console.error(error);
    }
  }

  save(value: any) {
    console.log(this.data);
    if (this.data && this.data.file && this.data.file.data) {
      let resData = this.data.file.data;
      let entity = { id: resData.id, userAuthority: 0, groupAuthority: 0, otherAuthority: 0 };
      if (value.user) {
        value.user.forEach((element: any) => {
          entity.userAuthority += element;
        });
      } else {
        entity.userAuthority = 7;
      }
      if (value.group) {
        value.group.forEach((element: any) => {
          entity.groupAuthority += element;
        });
      }
      if (value.other) {
        value.other.forEach((element: any) => {
          entity.otherAuthority += element;
        });
      }
      console.log(entity);
      this.http.put(`${Api.BaseNodeApi}/permission`, null, entity).subscribe(res => {
        if (res.code === ResponseCode.SUCCESS) {
          this.data = res.data;
          this.msgSrv.success('成功！');
          this.modal.close(true);
        } else {
          this.msgSrv.error(res.message ? res.message : '失败，未知原因！');
        }
      });
    } else {
      this.msgSrv.warning('请先确保文件已上传成功');
    }
  }

  async uploadFile(file: File, callback?: UploadProgressCallback) {
    // 计算md5中
    callback && callback(0, 'preparation');
    const chunks = await cutFile(file, progress => {
      callback && callback(0, 'preparation', '', progress);
    });
    const merkleTree = new MerkleTree(chunks.map(chunk => chunk.hash));
    const md5 = merkleTree.getRootHash();
    const partMd5List = merkleTree.leafs;
    const chunkFileList = chunks.map(chunk => chunk.blob);

    const needUploadFile: ChunkFileUrlType[] = [];

    const data = {
      fileMd5: md5,
      fullFileName: file.name,
      fileSize: file.size
    };
    // 计算md5中
    callback && callback(0, 'preupload', md5, 100);
    const md5Observable = this.http.post(`${Api.BaseOssFileApi}/upload/init`, data);
    const result = await firstValueFrom<ResponseBody<UploadFileInfo>>(md5Observable);
    console.log(result);
    // return
    if (result.code === ResponseCode.SUCCESS) {
      const uploadInfo = result.data;
      if (uploadInfo) {
        const partList = uploadInfo.partList;
        if (!uploadInfo.isDone) {
          // 未完成
          if (partList) {
            if (partList.length > 0) {
              chunkFileList.forEach((item, index) => {
                const url = `${partList[index].url}`;
                needUploadFile.push({ url, file: item });
              });
              // plimit 并发上传
              const uploadLimit = needUploadFile.map(n => this.limit(() => this.uploadChunkUrl(n, file.type, callback)));
              const results = await Promise.allSettled(uploadLimit);
              const errResults = results.filter(r => r.status === 'rejected');
              if (errResults.length > 0) {
                console.warn(`${file.name} 上传失败的分片-----`, errResults);
                return;
              }
            }
            try {
              const completeObservable = this.http.post(
                `${Api.BaseOssFileApi}/upload/complete/${uploadInfo.fileKey}?parentId=${this.parentId}`,
                {
                  partMd5List: partMd5List.map(md5 => md5.h)
                }
              );
              const { code, data } = await lastValueFrom<ResponseBody<any>>(completeObservable);
              if (code === 200) {
                callback && callback(0, 'success');
                console.log(data);
              }
            } catch (error) {
              console.error(error);
            }
          }
        }
      }
      callback && callback(file.size, 'success');
      return true;
    } else {
      return false;
    }
  }

  // 根据分片上传地址将分片直传至 minio
  uploadChunkUrl(chunkItem: ChunkFileUrlType, type: string, callback?: UploadProgressCallback): Promise<void> {
    return new Promise((resolve, reject) => {
      const headers = { 'Content-Type': type || 'application/octet-stream' };
      this.http
        .put(
          chunkItem.url,
          chunkItem.file,
          {},
          {
            context: new HttpContext().set(ALLOW_ANONYMOUS, true).set(RAW_BODY, true),
            headers
          }
        )
        .subscribe((res: any) => {
          // if (res.status !== 200) {
          //   reject(chunkItem);
          // } else {
          // 进度更新
          callback && callback(chunkItem.file.size, 'uploading');
          resolve();
          // }
        });
    });
  }
}
